Skip to content

fix(mobile): preserve exact mention recipients in saved drafts - #7387

Open
loganj wants to merge 2 commits into
fix/mobile-exact-mentions-20260905from
fix/mobile-durable-mentions-20260905
Open

fix(mobile): preserve exact mention recipients in saved drafts#7387
loganj wants to merge 2 commits into
fix/mobile-exact-mentions-20260905from
fix/mobile-durable-mentions-20260905

Conversation

@loganj

@loganj loganj commented Sep 5, 2026

Copy link
Copy Markdown
Collaborator

🤖

Summary

Saved drafts remembered their text but not exactly who each mention referred to: reopening a draft (or restarting the app) could re-bind a mention to the wrong same-name teammate, and a draft restored in one thread could capture text arriving for another. This PR saves the exact selected identities alongside the draft text and restores them together, so a reopened draft names the same recipients — with their agent markings — that you picked, including after a failed send.

  • Persists selected keys and agent presentation in the app's existing per-account/channel/thread draft storage (device-local; no cross-device portability claim).
  • Restores recipient bindings before text listeners run, including recovery from a rejected send; malformed legacy entries are ignored rather than guessed.
  • Retires the previous draft's listeners before restoring another, so switching threads can no longer write incoming text into your outgoing draft.

Related issue

  • Fixes: N/A. No separate issue; the related work is the stack below.
  • Depends on fix(mobile): bind same-name mentions to exact selected identities #7385 (declared base) — same-name selections and qualified labels come from that PR.
  • Landing note: branches in this series overlap in the composer — when rebasing, keep exact/durable mention bindings, the explicit invite/reference-only choice, the account/visit/revision fences, and authorization before membership preparation and publication; don't resolve conflicts by taking either side wholesale.
  • Draft — not requesting merge yet; the security advisory doesn't run on this stacked base (it reviews main-based ranges).

Testing

  • Regressions cover two namesakes, restart, rename, rejected send, a second restart, and changing draft scope.
  • At the branch head: just mobile-check, the full mobile test suite, and full local just ci all pass — receipts in the exact-head evidence comment.
  • Verification is widget-test level; no native device or simulator run is claimed.

To see it: mention an agent or a namesake, leave the channel, restart the app, and return — the draft still names the exact recipients you picked.

Screenshots

Flutter production-widget test renders — not native-device screenshots or acceptance captures.

Scenario Before After
Draft reopened after leaving the channel and restarting the app Before: the restored draft shows plain @Helper Bot text — the exact selected recipient is lost After: the restored draft shows the highlighted robot mention chip for the exact selected recipient
Capture provenance

Rendered by the Flutter widget engine in a flutter test run (production widgets, production theme; no device or simulator). Before: this PR's declared base 39afd73b0adfde14164f4b10dbd089cb498312b6. After: its head 8a781108507dc4501192b0101185decc1ed6f118.

@loganj
loganj force-pushed the fix/mobile-durable-mentions-20260905 branch 2 times, most recently from 6fd3b66 to 5696495 Compare September 7, 2026 15:07
Signed-off-by: Logan Johnson <loganj@squareup.com>
Signed-off-by: Logan Johnson <loganj@squareup.com>
@loganj
loganj force-pushed the fix/mobile-durable-mentions-20260905 branch from 5696495 to 8a78110 Compare September 7, 2026 15:11
@loganj

loganj commented Sep 7, 2026

Copy link
Copy Markdown
Collaborator Author

Independent durable-mention readiness evidence — 2026-09-07

Verified current stacked range: fix/mobile-exact-mentions-20260905 at 39afd73b0adfde14164f4b10dbd089cb498312b68a781108507dc4501192b0101185decc1ed6f118; +274/−23 = 297 across 8 files. Still OPEN/draft. Reconciliation only — no source changes, pushes, or test reruns.

  • Local, exact final head (isolated TMPDIR and worktree-local targets): just mobile-check exit 0; full just mobile-test exit 0 (2,084 passed); full just ci exit 0. Receipts: WORK_LOGS/MENTIONS_REVIEW_A24EBE91/7387/final/ (results.txt: mobile-check=0, mobile-test=0, ci=0). The rebase from 6fd3b66b kept a byte-identical incremental diff (SHA-256 recorded).
  • Durable-binding semantics covered by regressions on this head: one preferences snapshot for text + exact bindings, binding-only replacement, restoration before text notify, owner/account/community/thread-fenced listeners, failed-send recovery, and a second restart before retry.
  • Remote: CI run 34137043290 completed success at this exact head. Rollup: 30 success / 25 intentional skips; nothing pending or failing.
  • Rendered-chip acceptance also passed 39/39 at this head (same matrix as fix(mobile): bind same-name mentions to exact selected identities #7385, including the Desktop fix(desktop): restore mention chip identity icons #7338 @/robot glyph-regression parity; see the fix(mobile): bind same-name mentions to exact selected identities #7385 evidence comment).
  • Advisory security: intentionally ineligible on this stacked (non-main) base — the trusted workflow rejects non-main bases and its skipped jobs are not required merge gates. No bypass or base change was attempted. The parent's authorized advisory attempt did not complete (40m0s timeout, no findings); see fix(mobile): bind same-name mentions to exact selected identities #7385.
  • The PR body's composed-stack reference (40630565) predates the mention repairs and is stale relative to this head; no new composed-tree testing is claimed.
  • Widget evidence only; no native device/simulator. Draft retained; no merge requested.

2026-09-07 update: the stale composed reference described above has been retired — the verify/mobile-combined-20260905 remote branch at 40630565 was deleted and the composed-stack sections were removed from all 11 PR bodies (no replacement combined-testing claim was made). This comment's 2026-09-07 evidence statements are preserved as historical record. Receipt: WORK_LOGS/COMPOSED_REFERENCE_RETIREMENT_A4B214BD/REPORT.md.

@jedwards27 jedwards27 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:bot: Jude’s code review agent — request changes

Reviewed base 39afd73b0adfde14164f4b10dbd089cb498312b6 through exact head 8a781108507dc4501192b0101185decc1ed6f118.

Major — saved display classification can authorize a later membership mutation

DraftMention.isAgent is persisted as device-local JSON (mobile/lib/shared/mentions/draft_mention.dart:3-24) and restored directly into MentionCandidate.isAgent (mobile/lib/features/channels/compose_bar/draft_lifecycle.dart:97-109). On send, a currently non-member candidate with that restored bit is classified into agentPubkeys (mobile/lib/features/channels/compose_bar/helpers.dart:541-551) and silently added with role=bot (helpers.dart:606-617, via _addMentionedNonMembers at lines 436-474).

That makes stale presentation metadata an authorization/provenance input. A draft can be saved while a pubkey is an eligible directory agent, then reopened after that directory or verified classification disappears. Sending still emits a durable kind 9000 bot-membership mutation instead of using the human invite / reference-only path. The relay does not repair this trust boundary: its PUT_USER policy permits an authorized channel member to assign the bot role (crates/buzz-relay/src/handlers/channel_authz.rs:119-166). This also contradicts DraftMention's own contract that display classification is not authorization.

Two independent production-widget probes reproduced the stale-provenance path at this head: persisted is_agent: true, no current member/directory-agent evidence, then reopen/send emitted kind 9000 with the target p tag and role=bot, without a human confirmation dialog. The checked-in restart/rejected-send regression does not exercise classification changing between save and send.

Required change: Preserve the exact label-to-pubkey binding, but re-derive agent status and mention eligibility at send from current authoritative evidence (current membership role and current verified directory/NIP-OA evidence). If current evidence cannot establish agent status, fail closed into the human invite/reference-only path or another explicit safe choice. Add a production-seam regression covering: eligible agent selected → draft saved → current agent provenance removed/changed → restart/reopen/send → no silent bot-role PUT_USER, with the intended fallback asserted.

Validation

  • Focused mobile production-seam suites: 129 passed on a clean tree with head checked before and after as 8a781108507dc4501192b0101185decc1ed6f118.
  • The new checked-in widget regression exact draft recipients survive restart and failed-send recovery: passed independently.
  • git diff --check 39afd73b0adfde14164f4b10dbd089cb498312b6..8a781108507dc4501192b0101185decc1ed6f118: passed.
  • GitHub Mobile CI and required checks are green on this head.
  • Supplemental full mobile suite: 2,084 passed, but its post-run SHA could not be re-attested after the disposable worktree metadata was removed, so it is not relied upon for this verdict.

Confidence gap: no native iOS simulator/device journey was observed, so keyboard, VoiceOver, and native lifecycle behavior remain unverified. This is verification outstanding, not separate author rework.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants